package com.glink.manage.service.Impl;

import com.auth0.jwt.interfaces.Claim;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.glink.common.contants.BaseCodeConstants;
import com.glink.common.contants.BaseConstants;
import com.glink.common.exception.GeneralSimpleException;
import com.glink.common.util.LogUtil;
import com.glink.manage.common.*;
import com.glink.manage.domain.CryptoBean;
import com.glink.manage.domain.UserBean;
import com.glink.manage.dto.log.LogSaveDTO;
import com.glink.manage.dto.user.LoginDTO;
import com.glink.manage.dto.user.UserRestPwdDTO;
import com.glink.manage.dto.user.UserUpdatePwd2DTO;
import com.glink.manage.dto.user.UserUpdatePwdDTO;
import com.glink.manage.mapper.CryptoMapper;
import com.glink.manage.service.*;
import com.glink.manage.vo.user.UserVO;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @Author : qiushaoshan
 * @create 2024/6/19 11:12
 */
@Slf4j
@Service
public class SecurityServiceImpl extends ServiceImpl<CryptoMapper, CryptoBean> implements SecurityService {

    public static final int INT_20 = 20;
    public static final int INT_3 = 3;
    

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private UserService userService;
    
    @Autowired
    private PasswordService passwordService;
    
    @Autowired
    private LogService logService;
    
    @Autowired
    private OrgService orgService;

    /**
     * 获取公钥
     * @param userName 账号
     * @return 公钥信息
     * @throws GeneralSimpleException 异常信息
     */
    @Override
    public String findOnePublicKey(String userName) throws GeneralSimpleException {
        UserBean userBean = userService.lambdaQuery().eq(UserBean::getUserName, userName).one();
        if(Objects.nonNull(userBean)){
            userName = userBean.getId();
        }
        return getSecretKey(userName, CryptoBean::getPublicKey);
    }

    /**
     * 获取私钥
     *
     * @param userName 账号
     * @return 私钥
     */
    @Override
    public String findOnePrivateKey(String userName) throws GeneralSimpleException {
        UserBean userBean = userService.lambdaQuery().eq(UserBean::getUserName, userName).one();
        if(Objects.nonNull(userBean)){
            userName = userBean.getId();
        }
        return getSecretKey(userName, CryptoBean::getPrivateKey);
    }

    /**
     * 获取密钥
     * @param account 账号
     * @param column 字段：私钥或公钥
     * @return 密钥值
     */
    private String getSecretKey(String account, SFunction<CryptoBean, String> column) throws GeneralSimpleException {
        String secretKey = "";
        int cryptoId = (Math.abs(account.hashCode()) % 16) + 1;
        CryptoBean cryptoBean = this.lambdaQuery().eq(CryptoBean::getId, cryptoId).select(column).one();
        if(Objects.nonNull(cryptoBean)){
            if(StringUtils.isNotBlank(cryptoBean.getPrivateKey())){
                secretKey = cryptoBean.getPrivateKey();
            }else if(StringUtils.isNotBlank(cryptoBean.getPublicKey())){
                secretKey = cryptoBean.getPublicKey();
            }
        }
        if(StringUtils.isBlank(secretKey)){
            throw new GeneralSimpleException("400009", account);
        }
        return secretKey;
    }

    @Override
    public List<String> randomKey() {
        List<String> result = Lists.newArrayList();
        for (int i = 0; i < INT_20; i++) {
            String value = RandomStringUtils.randomAlphanumeric(4);
            String key = StringUtils.join(Constants.RANDOM_KEY_PREFIX, value);
            if (!redisUtil.hasKey(key)) {
                redisUtil.set(key, value, 10, TimeUnit.MINUTES);
                result.add(value);
                if (result.size() == INT_3) {
                    break;
                }
            } else {
                log.info(LogUtil.encode("随机" + key + "已存在,寻找下一"));
            }
        }
        return result;
    }

    @Override
    public String generateToken(LoginDTO loginDTO, String ip) throws GeneralSimpleException {
        String userName = loginDTO.getUserName();
        String password = loginDTO.getPassword();
        UserVO userVO = userService.findByUserName(userName);
        String userId = extractAndCheckPassword(userVO, userName, password);
        
        String logMsg = LogRecordUtils.login(userVO.getFullName());
        
        // 添加系统登录日志
        logService.addLogRecord(userId, new LogSaveDTO(BaseCodeConstants.CodeType_LogType.KEY_LogType01, logMsg, ip));

        return generateJwToken(ip, userName);
    }

    /**
     * 返回用户id
     *
     * @param userVO 用户信息
     * @return 用户ID
     * @throws GeneralSimpleException 异常信息
     */
    private String extractAndCheckPassword(UserVO userVO, String userName, String password) throws GeneralSimpleException {
       
        if(Objects.isNull(userVO)){
            throw new GeneralSimpleException("400010", userName);
        }
        
        if (!StringUtils.equalsIgnoreCase(userVO.getStatus(), BaseCodeConstants.CodeType_UserStatus.KEY_UserStatus01)) {
            throw new GeneralSimpleException("400010", userName);
        }
        
        // 用户所属组织-不存在在，则不能登录
        if (StringUtils.isNotBlank(userVO.getOrgId())) {
            boolean status = orgService.checkOrg(userVO.getOrgId());
            if(!status){
                throw new GeneralSimpleException("300011", userName);    
            }
        }else{
            throw new GeneralSimpleException("300011", userName);
        }
        
        try {
            String privateKey = findOnePrivateKey(userName);
            password = passwordService.sm3(userVO.getId() + passwordService.onlyDecryptAndRemoveKey(privateKey, password) + BaseConstants.MD5_SALT);
        } catch (GeneralSimpleException e) {
            e.printStackTrace();
            log.error("解密失败", e);
            throw e;
        }
        if (!StringUtils.equals(password, userVO.getPwd())) {
            log.error("登录用户密码错误！");
            throw new GeneralSimpleException("400014");
        }

        // 校验密码是否符合规则，不符合规则，则需要重置
        String defaultPwd = passwordService.generateBySm3(passwordService.getDefaultPassword(), userVO.getId());
        if (StringUtils.equals(userVO.getPwd(), defaultPwd)) {
            try {
                CheckDataUtil.throwIfPwdNotRule(passwordService.getDefaultPassword());
            }catch (Exception e){
                log.info("密码不符合规则 id-{}", userVO.getId());
                throw new GeneralSimpleException("200009");
            }
            throw new GeneralSimpleException("200008");
        }
        
        return userVO.getId();
    }

    /**
     * 生成 jwtToken
     *
     * @param ip IP地址
     * @param userName 账号
     * @return
     */
    public String generateJwToken(String ip, String userName) {
        //step 2 生成jwt token
        String random = RandomStringUtils.randomAlphanumeric(8);
        String jwtToken = JwtUtil.generateToken(userName, random);
        log.info("生成的JwtToken: {}", jwtToken);
        //step 3  写入redis 记录登录信息
        String key = BaseConstants.REDIS_USER_TOKEN_PREFIX + userName + "_" + random;
        redisUtil.set(key, ip, JwtUtil.EXPIRE_TIME_IN_SECOND, TimeUnit.SECONDS);
        return jwtToken;
    }

    @Override
    public void logout(String jwtToken, String remoteIp) {
        try {
            //step 1 验证是否有效
            JwtUtil.verify(jwtToken);
            //step 2 验证是否过期
            JwtUtil.isJwtExpired(jwtToken);
            //step 3 解析获得值
            Map<String, Claim> map = JwtUtil.parseJWT(jwtToken);
            String userName = map.getOrDefault(JwtUtil.LOGIN_NAME, null).asString();
            String randomOld = map.getOrDefault(JwtUtil.RANDOM, null).asString();
            String key = BaseConstants.REDIS_USER_TOKEN_PREFIX + userName + "_" + randomOld;


            UserVO userVO = userService.findByUserName(userName);
            if(Objects.nonNull(userVO)){
                String logMsg = LogRecordUtils.logout(userVO.getFullName());
                logService.addLogRecord(userVO.getId(), new LogSaveDTO(BaseCodeConstants.CodeType_LogType.KEY_LogType01, logMsg, remoteIp));    
            }
            
            redisUtil.del(key);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("注销错误 ", e);
        }
    }

    @Override
    public Long validTime(String jwtToken) {
        //step 1 验证是否有效
        JwtUtil.verify(jwtToken);
        //step 2 验证是否过期
        JwtUtil.isJwtExpired(jwtToken);
        //step 3 解析获得值
        Map<String, Claim> map = JwtUtil.parseJWT(jwtToken);
        String userName = map.getOrDefault(JwtUtil.LOGIN_NAME, null).asString();
        String randomOld = map.getOrDefault(JwtUtil.RANDOM, null).asString();
        String key = BaseConstants.REDIS_USER_TOKEN_PREFIX + userName + "_" + randomOld;

        long expire = redisUtil.getExpire(key);
        if(expire >= 0){
            //获取用户信息是否为空
            Object obj = redisUtil.get(key);
            if (Objects.isNull(obj)) {
                log.error("获取Token剩余有效时,.redis找不到用户信息 key：{}", key);
                return -1L;
            }
        }

        return expire;
    }

    /**
     * 修改密码
     *
     * @param pwdDTO 密码信息
     * @return 修改状态
     */
    @Override
    @Transactional(value = "transactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Boolean updatePwd(UserUpdatePwdDTO pwdDTO) throws GeneralSimpleException {

        List<UserBean> userBeanList = userService.lambdaQuery().eq(UserBean::getUserName, pwdDTO.getUserName()).list();
        if(CollectionUtils.isEmpty(userBeanList)){
            throw new GeneralSimpleException("400020", pwdDTO.getUserName());
        }
        UserBean userBean = userBeanList.get(0);
        String userId = userBean.getId();
        String userOldPwd = userBean.getPwd();
        String privateKey = findOnePrivateKey(userBean.getId());
        
        // step 0 密码密文解密
        decrypt(privateKey, pwdDTO);
        // step 1 旧密码是否正确
        throwIfOldPwdError(userId, userOldPwd, pwdDTO.getOldPassword());
        
        // step 2 新密码是否符合规则
        CheckDataUtil.throwIfPwdNotRule(pwdDTO.getNewPassword());
        
        // step 3 更新旧密码
        String pwd = passwordService.generateBySm3(pwdDTO.getNewPassword(), userId);
        log.info("修改后的密码为: {}", pwd);
        
        userService.lambdaUpdate().set(UserBean::getPwd, pwd).eq(UserBean::getId, userId).update();

        log.info("当前密码变化,需要删除Token");
        clearTokenInRedis(userBean.getUserName());

        return true;
    }

    /**
     * 重置密码
     * @param restPwdDTO 重置密码信息
     * @param userId 当前用户ID
     * @return 状态
     */
    @Override
    @Transactional(value = "transactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Boolean restPasswd(UserRestPwdDTO restPwdDTO, String userId) throws GeneralSimpleException {

        UserBean userBean = userService.lambdaQuery().eq(UserBean::getId, userId).one();
        if(Objects.isNull(userBean)){
            throw new GeneralSimpleException("400020", userId);
        }
        List<UserBean> tempBeanList = userService.lambdaQuery().in(UserBean::getId, restPwdDTO.getUserIdList()).list();
        if(Objects.isNull(tempBeanList)){
            throw new GeneralSimpleException("400020", userId);
        }
        
        try {
            String privateKey = findOnePrivateKey(userId);
            String enablePassword = passwordService.onlyDecryptAndRemoveKey(privateKey, restPwdDTO.getPassword());
            throwIfOldPwdError(userId, userBean.getPwd(), enablePassword);    
        }catch (Exception e){
            throw new GeneralSimpleException("400025");
        }

        tempBeanList.forEach(tempBean -> {
            String newPassword = passwordService.generateBySm3(passwordService.getDefaultPassword(), tempBean.getId());

            userService.lambdaUpdate().set(UserBean::getPwd, newPassword).eq(UserBean::getId, tempBean.getId()).update();

            log.info("当前密码变化,需要删除Token");
            clearTokenInRedis(tempBean.getUserName());
        });

        return true;
    }

    /**
     * 修改密码
     *
     * @param userId 用户ID
     * @param pwd2DTO 密码
     * @return 状态
     */
    @Override
    public Boolean updatePwd2(String userId, UserUpdatePwd2DTO pwd2DTO) throws GeneralSimpleException {
        List<UserBean> userBeanList = userService.lambdaQuery().eq(UserBean::getId, userId).list();
        if(CollectionUtils.isEmpty(userBeanList)){
            throw new GeneralSimpleException("400020", userId);
        }
        UserBean userBean = userBeanList.get(0);
        String userOldPwd = userBean.getPwd();
        String privateKey = findOnePrivateKey(userBean.getId());

        UserUpdatePwdDTO pwdDTO = new UserUpdatePwdDTO();
        pwdDTO.setUserName(userBean.getUserName());
        pwdDTO.setOldPassword(pwd2DTO.getOldPassword());
        pwdDTO.setNewPassword(pwd2DTO.getNewPassword());
        pwdDTO.setNewPassword2(pwd2DTO.getNewPassword2());
        
        // step 0 密码密文解密
        decrypt(privateKey, pwdDTO);
        // step 1 旧密码是否正确
        throwIfOldPwdError(userId, userOldPwd, pwdDTO.getOldPassword());

        // step 2 新密码是否符合规则
        CheckDataUtil.throwIfPwdNotRule(pwdDTO.getNewPassword());

        // step 3 更新旧密码
        String pwd = passwordService.generateBySm3(pwdDTO.getNewPassword(), userId);
        log.info("修改后的密码为: {}", pwd);

        userService.lambdaUpdate().set(UserBean::getPwd, pwd).eq(UserBean::getId, userId).update();

        log.info("当前密码变化,需要删除Token");
        clearTokenInRedis(userBean.getUserName());

        return true;
    }

    /**
     * 密码发生变更，清除redis中token信息
     * @param userName 用户账号
     */
    public void clearTokenInRedis(String userName) {
        String pattern = BaseConstants.REDIS_USER_TOKEN_PREFIX + userName + "*";
        Set<Object> keys = redisUtil.keys(pattern);

        String[] keyArray = new String[keys.size()];
        int i = 0;
        for (Object key : keys) {
            keyArray[i ++] = (String) key;
        }
        redisUtil.del(keyArray);
    }
    /**
     * 解密密文
     *
     * @param pwdDTO 密码信息
     * @throws GeneralSimpleException 异常信息
     */
    private void decrypt(String privateKey, UserUpdatePwdDTO pwdDTO) throws GeneralSimpleException {
        pwdDTO.setOldPassword(passwordService.onlyDecryptAndRemoveKey(privateKey, pwdDTO.getOldPassword()));
        pwdDTO.setNewPassword(passwordService.onlyDecryptAndRemoveKey(privateKey, pwdDTO.getNewPassword()));
        pwdDTO.setNewPassword2(passwordService.onlyDecryptAndRemoveKey(privateKey, pwdDTO.getNewPassword2()));
        // 新密码不能和旧密码一致
        if (StringUtils.equals(pwdDTO.getOldPassword(), pwdDTO.getNewPassword())) {
            throw new GeneralSimpleException("400022");
        }
        
        // 确认密码要保持一致
        if (!StringUtils.equals(pwdDTO.getNewPassword2(), pwdDTO.getNewPassword())) {
            throw new GeneralSimpleException("400021");
        }
    }


    /**
     * 判断旧的密码是否正确
     * @param userId 用户ID
     * @param recordPwd 数据库记录的密码
     * @param oldPwd 录入的旧密码
     * @throws GeneralSimpleException 异常信息
     */
    private void throwIfOldPwdError(String userId, String recordPwd, String oldPwd) throws GeneralSimpleException {
        log.info("throwIfOldPwdError recordPwd-{}  oldPassword-{}", recordPwd, oldPwd);

        if (!Objects.equals(recordPwd, passwordService.generateBySm3(oldPwd, userId))) {
            log.info("旧密码不正确 id-{}", userId);
            throw new GeneralSimpleException("400023");
        }
    }
}
